using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Xml.Serialization;
using Game500.ClientServerCommon.Src;
using Game500.Server;
using log4net;

namespace Game500.StServer.Src
{
  public class Server
  {
    private const int ScreenWidth = 1600;
    private const int ScreenHeight = 1200;
    private const int FrameRefreshTime = 1000 / 60;
    private const string ConfigFileName = "ServerConfig.xml";
    private static readonly ILog Log = LogManager.GetLogger(typeof (Server));
    private readonly Core myCore = new Core();
    private readonly BaseMapManager myMapManager;
    private readonly NetworkManager myNetworkManager;
    private readonly List<NetworkPlayer> myPlayers = new List<NetworkPlayer>();
    private bool myCanContinueLoop = true;
    private readonly Map myMap;
    private readonly int[] myPlayerIds; 

    public Server(Guid mapId, string gameMessage, BaseMapManager mapManager)
    {
      myMapManager = mapManager;
      myNetworkManager = NetworkManager.Create();
      myMap = mapManager.GetMap(mapId);
      myPlayerIds = Enumerable.Repeat(-1, myMap.MaxPlayers).ToArray();
      GameInfo = new NetworkGameInfo(Guid.NewGuid(), myMap.MaxPlayers, new IPEndPoint(IPAddress.Loopback, myNetworkManager.Port), mapId, gameMessage);
    }

    private Server(BaseMapManager mapManager)
    {
      myMapManager = mapManager;
      myNetworkManager = NetworkManager.Create();
      myMap = myMapManager.DefaultMap;
      myPlayerIds = Enumerable.Repeat(-1, myMap.MaxPlayers).ToArray();
      
      GameInfo = new NetworkGameInfo(Guid.NewGuid(), myMap.MaxPlayers, new IPEndPoint(IPAddress.Loopback, myNetworkManager.Port), mapManager.DefaultMapGuid, "No message");
    }

    public NetworkGameInfo GameInfo { get; private set; }


    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    private static void Main()
    {
      var server = new Server(new BaseMapManager());
      server.Initialize();
      server.Run();
    }

    public void Run()
    {
      Map map = myMapManager.GetMap(GameInfo.MapId);
      myCore.SetBounds(map.MapWidth, map.MapHeight + map.YOffset);
      foreach (var o in map.Objects)
      {
        if (o.Polygon != null)
        {
          myCore.AddStatic(o.Position, o.Polygon.Select
            (p => new Microsoft.Xna.Framework.Vector2(p.X * o.Width, p.Y * o.Height)));
        }
      }
      float deltaTime = FrameRefreshTime;
      var fullWatch = new Stopwatch();
      LoadSettings();
      Log.Info("Entering mainloop");
      while (myCanContinueLoop)
      {
        fullWatch.Reset();
        fullWatch.Start();
        lock (myPlayers) Update(deltaTime);
        Thread.Sleep(FrameRefreshTime);
        fullWatch.Stop();
        deltaTime = fullWatch.ElapsedMilliseconds / 1000f;

#if DEBUG
        if (Console.KeyAvailable)
        {
          ConsoleKeyInfo key = Console.ReadKey(true);
          if (key.Key == ConsoleKey.F5)
          {
            LoadSettings();
          }
        }
#endif
      }
// ReSharper disable FunctionNeverReturns
    }

// ReSharper restore FunctionNeverReturns


    private void LoadSettings()
    {
      if (File.Exists(ConfigFileName))
      {
        Log.Info("Loading settings from " + ConfigFileName);
        try
        {
          var deserializer = new XmlSerializer(myCore.Parameters.GetType());
          using (TextReader textReader = new StreamReader(ConfigFileName))
          {
            myCore.Parameters = (Parameters)deserializer.Deserialize(textReader);
          }
        }
        catch (Exception e)
        {
          Log.Error("Failed to load settings. ", e);
        }
      }
      else
      {
        Log.Info("Saving settings to " + ConfigFileName);
        try
        {
          var serializer = new XmlSerializer(myCore.Parameters.GetType());
          using (TextWriter textWriter = new StreamWriter(ConfigFileName))
          {
            serializer.Serialize(textWriter, myCore.Parameters);
          }
        }
        catch (Exception e)
        {
          Log.Error("Failed to save settings. ", e);
        }
      }
    }

    private void OnPlayerLeft(NetworkPlayer player)
    {
      lock (myPlayers)
      {
        lock (GameInfo)
        {
          var index = Array.IndexOf(myPlayerIds, player.CoreId.ID);
          if (index == -1) return;
          myPlayerIds[index] = -1;

          myCore.DeleteClient(player.CoreId);
          // TODO: double player deletion. WTF??
          if (!myPlayers.Remove(player)) return; // throw new Exception("Player was not deleted");
          GameInfo.ActivePlayers--;
          if (GameInfo.ActivePlayers == 0) Terminate();
        }
      }
    }

    private void Terminate()
    {
      Log.Info("Terminating session");
      myCanContinueLoop = false;
      myNetworkManager.Dispose();
    }

    private SceneFrame CreateBaseFrame()
    {
      var frame = new SceneFrame();
	  frame.Score = myCore.GetFrags();
      return frame;
    }

    private void Update(float deltaTime)
    {
      foreach (NetworkPlayer player in myPlayers)
      {
        IEnumerable<GamerAction> actions = player.GetActions();
        foreach (GamerAction action in actions)
        {
          myCore.DoAction(player.CoreId, action);
        }
        player.ResetActions();
      }

      myCore.Simulate(deltaTime);

      IEnumerable<SceneObject> objects = myCore.GetObjects();
      SceneFrame frame = CreateBaseFrame();
      foreach (SceneObject obj in objects)
      {
        var descriptor = new ObjectDescriptor(obj.ID, (int)obj.ResourceID) {Position = obj.Position, Rotation = obj.Rotation};
        var plane = obj as PlaneObject;
        if (plane != null)
        {
          descriptor.Flip = plane.Flipped;
          descriptor.Visibility = plane.Visible;
        }
        frame.DataSection.Add(descriptor);
      }

      IEnumerable<int> exploded = myCore.GetExplodedObjects();
      var exp = new ExplosionsEvent();
      exp.ObjectIds.AddRange(exploded);

      var shots = new ShotsEvent();
      IEnumerable<int> coreShots = myCore.GetShots();
      shots.ShooterIds.AddRange(coreShots);

      foreach (NetworkPlayer player in myPlayers)
      {
        player.SendFrame(frame);
		if (shots.ShooterIds.Count > 0)
			player.SendShots(shots);
		if (exp.ObjectIds.Count > 0)
			player.SendExplosions(exp);
      }
    }

    public bool TryBind(int minPort, int maxPort)
    {
      bool result = 0 != myNetworkManager.BindToPortRange(minPort, maxPort);
      if (result) GameInfo.ServerAddress.Port = myNetworkManager.Port;
      return result;
    }

    public void Initialize()
    {
      Log.Info("Server starting...");
      myNetworkManager.PlayerLeft += OnPlayerLeft;
      myNetworkManager.PlayerJoined += OnPlayerJoined;

      myNetworkManager.Start();
    }

    private void OnPlayerJoined(NetworkPlayer player)
    {
      lock (myPlayers)
      {
        lock (GameInfo)
        {
          if (GameInfo.ActivePlayers >= GameInfo.MaxPlayers)
          {
            player.SendPlayerLimitReached(myMap.MaxPlayers);
            return;
          }

          int index = Array.IndexOf(myPlayerIds, -1);

          myPlayers.Insert(0, player);

          var pos = myMap.PlayerStartPositions[index];

          player.CoreId = myCore.NewClient(pos.Position, pos.Rotation, pos.Flipped);
          myPlayerIds[index] = player.CoreId.ID;

          


          GameInfo.ActivePlayers++;
          Log.Info(String.Format("Player \"{0}\"(id = {1}) joined", player.Username, player.CoreId.ID));
          player.SendGameInfo(ScreenWidth, ScreenHeight, GameInfo.MapId);
        }
      }
    }
  }
}